﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.SqlClient;
using Persistencia.Interfaces;

namespace Persistencia
{
    public abstract class Factoria
    {
        private Dictionary<string, IPersistibleModelo> persistibles = new Dictionary<string, IPersistibleModelo>();
        private List<IPersistibleModelo> persistiblesEliminados = new List<IPersistibleModelo>();
        private TransaccionBase transaccion;
        protected bool todaLaBaseCargada=false;
        private Persistencia.Interfaces.UltimoId ultimoId;
        protected internal Persistencia.Interfaces.UltimoId UltimoId
        {
            get
            {
                if (this is IIdentityFactory)
                {
                    if (ultimoId == null)
                    {
                        ultimoId = new Interfaces.UltimoId();
                        SqlCommand commUltimoId = new SqlCommand("SELECT UltimoId FROM NUESTRA_RUBIA_ES_MEJOR.UltimoId WHERE NOMBRE_TABLA='" + this.NombreTabla.Trim()+"'" , this.Transaccion.Conexion);
                        List<HashTableNCSensitive> ht = this.Transaccion.EjecutarComando(commUltimoId, 0);
                        if (ht.Count != 0)
                            ultimoId.Valor = (int.Parse(ht[0]["UltimoId"].ToString()));
                        else
                        {
                            System.Collections.IEnumerator enume=this.ParametrosClave().Keys.GetEnumerator();
                            enume.MoveNext();
                            string key=enume.Current.ToString();
                            commUltimoId = new SqlCommand("SELECT TOP 1 "+key+" FROM "+ this.NombreTabla+" ORDER BY "+key+" DESC");
                            ht=this.Transaccion.EjecutarComando(commUltimoId, 1);
                            if (ht.Count != 0)
                            {
                                long id = (long)ht[0][key];
                                commUltimoId = new SqlCommand("INSERT INTO NUESTRA_RUBIA_ES_MEJOR.UltimoId VALUES ('" + this.NombreTabla.Trim() + "'," + id + ")");
                                this.Transaccion.EjecutarComandoSinRespuesta(commUltimoId);
                                ultimoId.Valor = id;
                            }
                            else
                            {
                                ultimoId.Valor = 0;
                            }
                        }
                    }
                }

                return ultimoId;
            }
        }

        public TransaccionBase Transaccion
        {
            get { return transaccion; }
            
        }

        protected Factoria(TransaccionBase transaccion)
        {
            this.transaccion = transaccion;
        }

        public void Suplantar(IPersistibleModelo iPer)
        {
            if (!Existe(iPer))
                throw new Exception("El objeto no se encuentra en esta factoria");

            this.persistibles[iPer.Persistible.Id] = iPer;
            iPer.Persistible.Suplantado = true;
        }

        protected void Agregar(IPersistibleModelo pers)
        {
            PersistibleDB objetoPers = new PersistibleDB();
            objetoPers.Factoria = this;
            objetoPers.Transaccion = this.transaccion;
            if (!(this is IIdentityFactory))
            {
                HashTableNCSensitive hts = new HashTableNCSensitive();
                pers.CargarParametrosClave(hts);
                objetoPers.Id = this.Id(hts);
            }
            else
            {
                ((IIdentityEntity)pers).Id =  (this.UltimoId.Valor + 1);
                objetoPers.Id = ((IIdentityEntity)pers).Id.ToString();
            }

            objetoPers.Objeto = pers;
            pers.Persistible = objetoPers;
            if (Existe(pers))
                throw new Exception("La clave ya existe: " + pers.Persistible.Id);

            persistibles.Add(pers.Persistible.Id, pers);
            if (this is IIdentityFactory)
                this.UltimoId.Valor++;
        }

        private bool Existe(IPersistibleModelo pers)
        {
            return persistibles.ContainsKey(pers.Persistible.Id);
        }

        protected abstract Predicate<IPersistibleModelo> FiltroXId(string id);

        public T SelectXId<T>(string id) where T : IPersistibleModelo
        {

            HashTableNCSensitive hts = this.ParametrosClave();
            string consulta = "SELECT * FROM " + this.NombreTabla + " WHERE 1=1 ";
            string[] claves=id.Split('|');
            int i = 0;
            foreach (String key in hts.Keys)
            {
                consulta += " AND " + key + "=" + claves[i];
                i++;
            }
            SqlCommand comm = new SqlCommand(consulta, this.Transaccion.Conexion);
            List<T> iPers = this.Select<T>(comm, this.FiltroXId(id));
            if(iPers.Count>0)
                return iPers.Last();

            return default(T);
        }
        public List<T> SelectTodos<T>() where T: IPersistibleModelo
        {
            SqlCommand comm = new SqlCommand("SELECT * FROM "+ this.NombreTabla, this.Transaccion.Conexion);
            List<T> iPers= this.Select<T>(comm, delegate(IPersistibleModelo iPer) { return true; });
            if (iPers.Count>0 && iPers[0] is IIdentityEntity)
                iPers.Sort(delegate(T iPer1, T iPer2)
                {
                    return ((IIdentityEntity)iPer1).Id.CompareTo(((IIdentityEntity)iPer2).Id);
                });
           // todaLaBaseCargada = true;
            return iPers;
        }

        public List<T> SelectTodosHabilitados<T>() where T : IPersistibleModelo
        {
            SqlCommand comm = new SqlCommand("SELECT * FROM " + this.NombreTabla.Trim() + " WHERE " + this.NombreTabla.Trim().Split('.')[1] + "_Habilitado = @valor", this.Transaccion.Conexion);
            comm.Parameters.AddWithValue("@valor", true);
            List<T> iPers = this.Select<T>(comm, delegate(IPersistibleModelo iPer) { return true; });
            // todaLaBaseCargada = true;
            return iPers;
        }

        protected List<T> Select<T>(string clausulaWHERE, Predicate<IPersistibleModelo> filtro, List<SqlParameter> parameters) where T:IPersistibleModelo
        {
            SqlCommand comm = new SqlCommand("SELECT * FROM " + this.NombreTabla + " " + clausulaWHERE);
            
            foreach (SqlParameter parameter in parameters)
                comm.Parameters.Add(parameter);
            
            return this.Select<T>(comm, filtro);
        }

        protected List<T> Select<T>(SqlCommand comm, Predicate<IPersistibleModelo> filtro) where T: IPersistibleModelo
        {
            return this.Select<T>(comm, filtro, 0);
        }

        private bool CoincidenRegistros(HashTableNCSensitive htsViejo, HashTableNCSensitive htsNuevo)
        {
            foreach (String s in htsViejo.Keys)
            {
                if (!String.Equals(htsNuevo[s], htsViejo[s]))
                    return false;
            }
            return true;
        }

        protected List<T> Select<T>(SqlCommand comm,Predicate<IPersistibleModelo> filtro, int cantidad ) where T: IPersistibleModelo
        {
            int cantidadRegistrosDB=0;
            if (!todaLaBaseCargada)
            {
                List<HashTableNCSensitive> resultado = this.transaccion.EjecutarComando(comm, cantidad);
                 cantidadRegistrosDB= resultado.Count;
                 List<HashTableNCSensitive> registrosPorCargar = new List<HashTableNCSensitive>();
                 foreach (HashTableNCSensitive registro in resultado)
                 {
                     if (!this.persistibles.ContainsKey(this.Id(registro)))
                         registrosPorCargar.Add(registro);
                     else
                     {
                         HashTableNCSensitive htsObjetoViejo= new HashTableNCSensitive();
                         this.persistibles[this.Id(registro)].CargarParametrosClave(htsObjetoViejo);
                         this.persistibles[this.Id(registro)].CargarParametrosNoClave(htsObjetoViejo);
                         if (!CoincidenRegistros(htsObjetoViejo, registro) && !this.persistibles[this.Id(registro)].Persistible.Suplantado)
                        {
                            this.persistibles.Remove(this.Id(registro));
                            registrosPorCargar.Add(registro);
                        }  
                     }
                 }

                 foreach (HashTableNCSensitive registro in registrosPorCargar)
                 {

                     IPersistibleModelo persistible = this.NuevoPersistible(registro);
                     PersistibleDB objetoPers = new PersistibleDB();
                     objetoPers.Factoria = this;
                     objetoPers.Transaccion = this.transaccion;
                     objetoPers.Id = this.Id(registro);
                     objetoPers.Objeto = persistible;
                     persistible.Persistible = objetoPers;
                     persistibles.Add(persistible.Persistible.Id, persistible);
                     persistible.Cargar(registro);
                     objetoPers.HashDatosOriginal = persistible.HashDatos;
                     
                 }
            }


                List<IPersistibleModelo> persFiltrado = this.persistibles.Values.ToList<IPersistibleModelo>().FindAll(filtro);
                List<T> pers=new List<T>();;
                if (cantidad == 0 || persFiltrado.Count == cantidad)
                    pers = persFiltrado.ConvertAll<T>(delegate(IPersistibleModelo iPer) { return (T)iPer; });
                else
                {
                    if (persFiltrado.Count > cantidad || cantidadRegistrosDB < cantidad)
                    {
                      List<IPersistibleModelo> persistiblesModelo  = (List<IPersistibleModelo>)persFiltrado.Take<IPersistibleModelo>(cantidad);
                        pers= persistiblesModelo.ConvertAll<T>(delegate(IPersistibleModelo iPer) { return (T)iPer; });
                    }
                    else
                    {
                        if (cantidadRegistrosDB == cantidad)
                            pers = Select<T>(comm, filtro, cantidad * 2);
                    }
                }

                return pers;

         
        }

        public abstract string NombreTabla { get; }

        protected internal abstract HashTableNCSensitive ParametrosClave();

        protected internal abstract HashTableNCSensitive ParametrosNoClave();

        protected abstract IPersistibleModelo NuevoPersistible(HashTableNCSensitive htsNc);

        protected abstract string Id(HashTableNCSensitive hts);

        internal void Persistir()
        {
            foreach (IPersistibleModelo pers in persistibles.Values)
                this.Persistir(pers);

            this.persistiblesEliminados.ForEach(delegate(IPersistibleModelo pers) { persistibles.Remove(pers.Persistible.Id); });

            
        }

        public void Eliminar(IPersistibleModelo iPers)
        {
            iPers.Persistible.Eliminado = true;
        }

        private void Persistir(IPersistibleModelo pers)
        {
            if (pers.Persistible.HashDatosOriginal == null)
            {
                pers.AntesInsert();
                pers.Insert();
                pers.DespuesInsert();
            }
            else
            {
                if (pers.Persistible.Eliminado)
                {
                    pers.AntesDelete();
                    pers.Delete();
                    pers.DespuesDelete();
                    persistiblesEliminados.Add(pers);
                }
                else if (pers.HashDatos != pers.Persistible.HashDatosOriginal)
                {
                    pers.AntesUpdate();
                    pers.Update();
                    pers.DespuesUpdate();
                }
            }

            pers.Persistible.HashDatosOriginal = pers.HashDatos;
        }

        internal void Refrescar(Factoria fac)
        {
            foreach (IPersistibleModelo pers in this.persistibles.Values)
            {
                if (fac.persistibles.ContainsKey(pers.Persistible.Id) && fac.persistibles[pers.Persistible.Id].HashDatos != pers.HashDatos)
                {
                    HashTableNCSensitive hts= new HashTableNCSensitive();
                    fac.persistibles[pers.Persistible.Id].CargarParametrosClave(hts);
                    fac.persistibles[pers.Persistible.Id].CargarParametrosNoClave(hts);
                    pers.Cargar(hts);

                }
            }
            
        }


    }
}
